<?php
namespace db;

use Exception;
use ArrayObject;
use IteratorAggregate;
use Countable;

/**
 * Query result that holds the result data (or will fetch it when necessary silently).
 * It has a set of methods that will help you to sepcify the format of data that you want to get.
 *
 * Some of methods returns independent data but some will return the same so when you modify it, its likely that
 * other returned data will be modified too. You should be aware of that when you are modifying it.
 *
 * It can be iterated in foreach like arrays, or used as argument of count() function.
 *
 * @author Jakub Dykowski
 *        
 */
class OldQueryResult implements IteratorAggregate, Countable {
	private $result;
	private $rows;
	private $conn;

	function __construct($result, $conn) {
		$this->result = $result;
		$this->conn = $conn;
	}

	function getIterator() {
		self::ensureFetched();
		return new \ArrayIterator($this->rows);
	}

	/**
	 *
	 * @return array of \ArrayObject
	 */
	function asList() {
		return self::many();
	}

	/**
	 *
	 * @return array of \ArrayObject
	 */
	function many() {
		self::ensureFetched();
		return $this->rows;
	}

	/**
	 *
	 * @return array of arrays
	 */
	function asArrays() {
		return self::asList();
	}

	/**
	 *
	 * @return array of objects
	 */
	function asObjects() {
		return self::asList();
	}

	function count() {
		// TODO affected_rows sometime is -1 (when print_r is calld on $this before ensureFetch is called, > -1 fixes it, but thats is wierd)
		return $this->conn->affected_rows > -1 ? $this->conn->affected_rows : $this->result->num_rows;
	}

	/**
	 *
	 * @return bool whether the result is empty
	 */
	function isEmpty() {
		return self::count() < 1;
	}

	/**
	 * Returns the result as one.
	 *
	 * @return ArrayObject or null
	 * @throws \Exception if there is more result
	 */
	function one() {
		if (($count = self::count()) > 1)
			throw new \Exception("query returned more than ONE result");
		
		self::ensureFetched();
		return $count ? $this->rows[0] : null;
	}

	/**
	 * * Returns first or speciefied field from the only result.
	 *
	 * @param string $name
	 *        	field name
	 * @throws \Exception if result is empty
	 * @return string
	 */
	function field($name = null) {
		$entity = self::one();
		if ($entity === null)
			throw new \Exception("result is empty, cannot get it's field");
		return $name ? $entity[$name] : $entity[0];
	}

	/**
	 * Returns assoc array (of key-value pairs).
	 *
	 * @param string $key
	 *        	field to be used as key
	 * @param string $value
	 *        	field to be used as value
	 * @return array
	 */
	function keyValue($key, $value) {
		$result = array();
		foreach (self::asList() as $row) {
			if (!isset($row[$key]))
				throw new Exception("key '$key' not found in query result");
			if (!isset($row[$value]))
				throw new Exception("key '$value' not found in query result");
			if (isset($result[$row[$key]]))
				throw new Exception("key '$value' isn't unique");
			$result[$row[$key]] = $row[$value];
		}
		return $result;
	}

	/**
	 * Groups the result by given arguments(fields) where that field will be key and the
	 * value will be an array of results with the same given field.
	 *
	 * @param array $by
	 *        	fields to group by
	 * @return array grouped results with key as grouped value
	 * @throws \InvalidArgumentException if arguments are less than 1 or field is not found in any result
	 */
	function group() {
		$args = func_get_args();
		if (count($args) < 1)
			throw new InvalidArgumentException("at least one field expected to group by");
		
		return self::groupArray(self::many(), $args);
	}

	/**
	 *
	 * @return int newly created id
	 * @throws Exception if primary ke is not autoimcrement
	 */
	function id() {
// 		if ($this->conn->insert_id == 0)
// 			throw new Exception("it is not an insert query result, or maybe the field is not autoincrement and id wasn't specified");
		return $this->conn->insert_id;
	}

	protected static function groupArray(&$toGroup, $args) {
		$by = array_shift($args);
		$grouped = array();
		foreach ($toGroup as &$row) {
			$key = $row[$by];
			if (!isset($key))
				throw new Exception("group key '$by' not found in query result");
			$grouped[$key][] = $row;
		}
		if (count($args))
			foreach ($grouped as $key => &$row)
				$grouped[$key] = self::groupArray($grouped[$key], $args);
		
		return $grouped;
	}

	protected function ensureFetched() {
		if (count($this->rows) < self::count()) {
			if ($this->rows == null)
				$this->rows = array();
			while ($row = $this->result->fetch_array(MYSQLI_ASSOC)) {
				$this->rows[] = new ArrayObject($row, ArrayObject::ARRAY_AS_PROPS);
			}
			$this->result->free_result();
		} else if (self::count() == 0 and $this->rows === null)
			$this->rows = array();
	}
}